package concurrency;//: concurrency/ToastOMatic.java
// A toaster that uses queues.

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static net.mindview.util.Print.print;

class Toast {
  public enum Status {DRY, BUTTERED, JAMMED}

  private Status status = Status.DRY;
  private final int id;

  public Toast(int idn) {
    id = idn;
  }

  public void butter() {
    status = Status.BUTTERED;
  }

  public void jam() {
    status = Status.JAMMED;
  }

  public Status getStatus() {
    return status;
  }

  public int getId() {
    return id;
  }

  public String toString() {
    return "Toast " + id + ": " + status;
  }
}

class ToastQueue extends LinkedBlockingQueue<Toast> {
}

class Toaster implements Runnable {
  private ToastQueue toastQueue;
  private static Lock lock = new ReentrantLock();
  private static int count = 0;
//  private static volatile int count = 0;
  private Random rand = new Random(47);

  public Toaster(ToastQueue tq) {
    toastQueue = tq;
  }

  public void run() {
    try {
      while (!Thread.interrupted()) {
        TimeUnit.MILLISECONDS.sleep(100 + rand.nextInt(500));
        Toast t = null;

//        // Make toast
//        t = new Toast(count++);
//        print(t);
//        // Insert into queue
//        toastQueue.put(t);

        //try lock
//        boolean flag = false;
//        try {
//          flag = lock.tryLock(100, TimeUnit.MILLISECONDS);
//          // Make toast
//          print("Toaster count:" + count + " " + flag);
//          t = new Toast(count++);
//          TimeUnit.MICROSECONDS.sleep(100);
//          print(t + " " + flag);
//          // Insert into queue
//          toastQueue.put(t);
//        } finally {
//          if (flag)
//            lock.unlock();
//        }

        //lock
        try {
          lock.lock();
          // Make toast
          print("Toaster count:" + count);
          t = new Toast(count++);
          TimeUnit.MICROSECONDS.sleep(100);
          print(t);
          // Insert into queue
          toastQueue.put(t);
        } finally {
          lock.unlock();
        }

        //volatile
//        toastQueue.put(new Toast(count++));
      }
    } catch (InterruptedException e) {
      print("Toaster interrupted");
    }
    print("Toaster off");
  }
}

// Apply butter to toast:
class Butterer implements Runnable {
  private ToastQueue dryQueue, butteredQueue;

  public Butterer(ToastQueue dry, ToastQueue buttered) {
    dryQueue = dry;
    butteredQueue = buttered;
  }

  public void run() {
    try {
      while (!Thread.interrupted()) {
        // Blocks until next piece of toast is available:
        Toast t = dryQueue.take();
        t.butter();
        print(t);
        butteredQueue.put(t);
      }
    } catch (InterruptedException e) {
      print("Butterer interrupted");
    }
    print("Butterer off");
  }
}

// Apply jam to buttered toast:
class Jammer implements Runnable {
  private ToastQueue butteredQueue, finishedQueue;

  public Jammer(ToastQueue buttered, ToastQueue finished) {
    butteredQueue = buttered;
    finishedQueue = finished;
  }

  public void run() {
    try {
      while (!Thread.interrupted()) {
        // Blocks until next piece of toast is available:
        Toast t = butteredQueue.take();
        t.jam();
        print(t);
        finishedQueue.put(t);
      }
    } catch (InterruptedException e) {
      print("Jammer interrupted");
    }
    print("Jammer off");
  }
}

// Consume the toast:
class Eater implements Runnable {
  private ToastQueue finishedQueue;
  private int counter = 0;

  public Eater(ToastQueue finished) {
    finishedQueue = finished;
  }

  public void run() {
    try {
      while (!Thread.interrupted()) {
        // Blocks until next piece of toast is available:
        Toast t = finishedQueue.take();
        // Verify that the toast is coming in order,
        // and that all pieces are getting jammed:
        if (t.getId() != counter++ ||
//        if (finishedQueue.size() != counter++ ||
            t.getStatus() != Toast.Status.JAMMED) {
          print(">>>> counter: " + counter);
          print(">>>> Error: " + t);
          System.exit(1);
        } else
          print("Chomp! " + t);
      }
    } catch (InterruptedException e) {
      print("Eater interrupted");
    }
    print("Eater off");
  }
}

public class ToastOMatic {
  public static void main(String[] args) throws Exception {
    ToastQueue dryQueue = new ToastQueue(),
        butteredQueue = new ToastQueue(),
        finishedQueue = new ToastQueue();
    ExecutorService exec = Executors.newCachedThreadPool();
    exec.execute(new Toaster(dryQueue));
    exec.execute(new Toaster(dryQueue));
    exec.execute(new Butterer(dryQueue, butteredQueue));
    exec.execute(new Jammer(butteredQueue, finishedQueue));
    exec.execute(new Eater(finishedQueue));
    TimeUnit.SECONDS.sleep(5);
    exec.shutdownNow();
  }
} /* (Execute to see output) *///:~
